package com.google.code.swingonfire.component.foldable;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JPanel;
import javax.swing.SwingConstants;

import com.google.code.swingonfire.image.ComponentBufferImageProvider;
import com.google.code.swingonfire.image.ImageProvider;
import com.google.code.swingonfire.utility.interpolation.Interpolation;
import com.google.code.swingonfire.utility.interpolation.Interpolations;

public class JFoldablePanel extends JPanel implements ActionListener
{

    private static final long serialVersionUID = 4331303866699581096L;

    private final JFoldableComponent foldableComponent;

    private Component contentComponent;
    private ImageProvider imageProvider;
    private int direction = SwingConstants.NORTH;
    private boolean folded = false;
    private boolean animate = true;
    private double duration = 0.25;
    private Interpolation interpolation = Interpolations.soft();
    private AbstractJFoldableTrigger triggerComponent;
    private boolean triggerMoving;
    private int triggerLocation;
    private String actionCommand;

    public JFoldablePanel()
    {
        this(null);
    }

    public JFoldablePanel(Component contentComponent)
    {
        super(new BorderLayout());

        foldableComponent = new JFoldableComponent(this);
        add(foldableComponent, BorderLayout.CENTER);

        if (contentComponent != null)
        {
            setContentComponent(contentComponent);
        }

        setTriggerComponent(new DefaultJFoldableTrigger());
    }

    public Component getContentComponent()
    {
        return contentComponent;
    }

    public void setContentComponent(Component contentComponent)
    {
        if (this.contentComponent != contentComponent)
        {
            if (this.contentComponent != null)
            {
                foldableComponent.remove(this.contentComponent);
            }

            Component oldContentComponent = this.contentComponent;
            this.contentComponent = contentComponent;

            imageProvider = new ComponentBufferImageProvider(contentComponent, false);

            if (this.contentComponent != null)
            {
                foldableComponent.add(this.contentComponent);
                foldableComponent.pack();
            }

            firePropertyChange("contentComponent", oldContentComponent, contentComponent);
        }
    }

    public ImageProvider getImageProvider()
    {
        return imageProvider;
    }

    public int getDirection()
    {
        return direction;
    }

    public void setDirection(int direction)
    {
        if (this.direction != direction)
        {
            int oldDirection = this.direction;
            this.direction = direction;

            setTriggerComponent(getTriggerComponent(), isTriggerMoving());

            firePropertyChange("direction", oldDirection, direction);
        }
    }

    public boolean isFolded()
    {
        return folded;
    }

    public void setFolded(boolean folded)
    {
        setFolded(folded, animate);
    }

    public void setFolded(boolean folded, boolean animate)
    {
        if (this.folded != folded)
        {
            boolean oldFolded = this.folded;
            this.folded = folded;

            foldableComponent.updateState(folded, animate);

            firePropertyChange("folded", oldFolded, folded);
            fireActionPerformed();
        }
    }

    public void toggleFolded()
    {
        toggleFolded(animate);
    }

    public void toggleFolded(boolean animate)
    {
        setFolded(!isFolded(), animate);
    }

    public boolean isAnimate()
    {
        return animate;
    }

    public void setAnimate(boolean animate)
    {
        if (this.animate != animate)
        {
            boolean oldAnimate = this.animate;
            this.animate = animate;

            firePropertyChange("animate", oldAnimate, animate);
        }
    }

    public double getDuration()
    {
        return duration;
    }

    public void setDuration(double duration)
    {
        if (this.duration != duration)
        {
            double oldDuration = this.duration;
            this.duration = duration;

            firePropertyChange("duration", oldDuration, duration);
        }
    }

    public Interpolation getInterpolation()
    {
        return interpolation;
    }

    public void setInterpolation(Interpolation interpolation)
    {
        if (this.interpolation != interpolation)
        {
            Interpolation oldInterpolation = this.interpolation;
            this.interpolation = interpolation;

            firePropertyChange("interpolation", oldInterpolation, interpolation);
        }
    }

    public AbstractJFoldableTrigger getTriggerComponent()
    {
        return triggerComponent;
    }

    public void setTriggerComponent(AbstractJFoldableTrigger triggerComponent)
    {
        setTriggerComponent(triggerComponent, isTriggerMoving());
    }

    protected void setTriggerComponent(AbstractJFoldableTrigger triggerComponent, boolean triggerMoving)
    {
        int triggerLocation;

        switch (getDirection())
        {
            case SwingConstants.NORTH:
                triggerLocation = (triggerMoving) ? SwingConstants.SOUTH : SwingConstants.NORTH;
                break;

            case SwingConstants.SOUTH:
                triggerLocation = (triggerMoving) ? SwingConstants.NORTH : SwingConstants.SOUTH;
                break;

            case SwingConstants.EAST:
                triggerLocation = (triggerMoving) ? SwingConstants.WEST : SwingConstants.EAST;
                break;

            case SwingConstants.WEST:
                triggerLocation = (triggerMoving) ? SwingConstants.EAST : SwingConstants.WEST;
                break;

            default:
                throw new IllegalArgumentException("Invalid folding direction");
        }

        if (this.triggerMoving != triggerMoving)
        {
            boolean oldTriggerMoving = this.triggerMoving;
            this.triggerMoving = triggerMoving;

            firePropertyChange("triggerMoving", oldTriggerMoving, triggerMoving);
        }

        setTriggerComponent(triggerComponent, triggerLocation);
    }

    protected void setTriggerComponent(AbstractJFoldableTrigger triggerComponent, int triggerLocation)
    {
        if ((this.triggerComponent != triggerComponent) || (this.triggerLocation != triggerLocation))
        {
            if (this.triggerComponent != null)
            {
                remove(this.triggerComponent);

                this.triggerComponent.removeActionListener(this);
                this.triggerComponent.setPanel(null);
            }

            AbstractJFoldableTrigger oldTriggerComponent = this.triggerComponent;
            int oldTriggerLocation = this.triggerLocation;
            this.triggerComponent = triggerComponent;
            this.triggerLocation = triggerLocation;

            if (this.triggerComponent != null)
            {
                switch (this.triggerLocation)
                {
                    case SwingConstants.NORTH:
                        add(this.triggerComponent, BorderLayout.NORTH);
                        break;

                    case SwingConstants.SOUTH:
                        add(this.triggerComponent, BorderLayout.SOUTH);
                        break;

                    case SwingConstants.EAST:
                        add(this.triggerComponent, BorderLayout.EAST);
                        break;

                    case SwingConstants.WEST:
                        add(this.triggerComponent, BorderLayout.WEST);
                        break;

                    default:
                        throw new IllegalArgumentException("Invalid trigger location");
                }

                this.triggerComponent.addActionListener(this);
                this.triggerComponent.setPanel(this);
            }

            firePropertyChange("triggerComponent", oldTriggerComponent, triggerComponent);
            firePropertyChange("triggerLocation", oldTriggerLocation, triggerLocation);
        }
    }

    public boolean isTriggerMoving()
    {
        return triggerMoving;
    }

    public void setTriggerMoving(boolean triggerMoving)
    {
        setTriggerComponent(getTriggerComponent(), triggerMoving);
    }

    public int getTriggerLocation()
    {
        return triggerLocation;
    }

    public void addFoldableListener(FoldableListener listener)
    {
        listenerList.add(FoldableListener.class, listener);
    }

    public void removeFoldableListener(FoldableListener listener)
    {
        listenerList.remove(FoldableListener.class, listener);
    }

    public void fireFoldingInProgress(FoldableEvent event)
    {
        for (FoldableListener listener : listenerList.getListeners(FoldableListener.class))
        {
            listener.foldingInProgress(event);
        }
    }

    public void addActionListener(ActionListener listener)
    {
        listenerList.add(ActionListener.class, listener);
    }

    public void removeActionListener(ActionListener listener)
    {
        listenerList.add(ActionListener.class, listener);
    }

    protected void fireActionPerformed()
    {
        Object[] listeners = listenerList.getListenerList();
        ActionEvent e = new ActionEvent(this, 0, actionCommand, System.currentTimeMillis(), 0);

        for (int i = listeners.length - 2; i >= 0; i -= 2)
        {
            if (listeners[i] == ActionListener.class)
            {
                ((ActionListener) listeners[i + 1]).actionPerformed(e);
            }
        }
    }

    public String getActionCommand()
    {
        return actionCommand;
    }

    public void setActionCommand(String actionCommand)
    {
        this.actionCommand = actionCommand;
    }

    @Override
    public void actionPerformed(ActionEvent e)
    {
        if (e.getSource() == triggerComponent)
        {
            toggleFolded();
        }
    }

}
